
#include <BOpenGL>
#include <BRender>
#include "member_BLight.h"
#include "member_BScene.h"
#include "member_BGroup.h"
#include "member_BNode.h"
#include "member_BShape.h"

using namespace BWE;

extern BArray<GLuint>	UnusedTextureObjects;
extern BArray<GLuint>	UnusedFrameBufferObjects;
extern BArray<GLuint>	UnusedRenderBufferObjects;

member_BLight::member_BLight(BLight* light)
{
	boss = light;
	shadowSize = 128;
	samples = 0;
	attenuation = 0;
	attenuation2 = 0;
	exponent = 0.0f;
	cutoff = 100.0f;
	radius = 100;
	near = 0;
	far = 0;
	halfSize = 0;

	dirty = true;
	resized = true;
	fbo = 0;
	rbo = 0;
	tbo = 0;
	msfbo = 0;
	msrbo = 0;
	mstbo = 0;
}
member_BLight::~member_BLight()
{

}

bool member_BLight::freshFrustum()
{
	if (cutoff >= 90 || radius < REAL_EPSILON)
		return false;
	if (dirty)
	{
		BReal threshold = BReal(0.01);
		BReal brightness = (ambient + diffuse + specular).gray() / BReal(255);

		far = radius;
		near = radius * BReal(0.0001);
		halfSize = tan(bDTR(cutoff)) * near;

		const BVector& pos = boss->position();
		direct = boss->matrix().mult3(1, 0, 0);
		direct.normalize();
		center = pos + direct;

		right = direct.cross(0, 0, 1);
		right.normalize();

		up = right.cross(direct).normal();
		BVector dir_up = up * halfSize;
		BVector dir_right = right * halfSize;

		BVector near_center = pos + direct * near;
		BVector near_v0 = near_center - dir_right + dir_up;
		BVector near_v1 = near_center - dir_right - dir_up;
		BVector near_v2 = near_center + dir_right - dir_up;
		BVector near_v3 = near_center + dir_right + dir_up;

		frustum.setNear(0, near_v0);
		frustum.setNear(1, near_v1);
		frustum.setNear(2, near_v2);
		frustum.setNear(3, near_v3);

		BReal ratio = far / near;
		BVector far_v0 = pos + (near_v0 - pos) * ratio;
		BVector far_v1 = pos + (near_v1 - pos) * ratio;
		BVector far_v2 = pos + (near_v2 - pos) * ratio;
		BVector far_v3 = pos + (near_v3 - pos) * ratio;

		frustum.setFar(0, far_v0);
		frustum.setFar(1, far_v1);
		frustum.setFar(2, far_v2);
		frustum.setFar(3, far_v3);

		frustum.setPlane(0, BPlane(direct, pos + direct * near));//near
		frustum.setPlane(1, BPlane(near_v0, pos, near_v1));//left
		frustum.setPlane(2, BPlane(near_v2, pos, near_v3));//right
		frustum.setPlane(3, BPlane(near_v3, pos, near_v0));//top
		frustum.setPlane(4, BPlane(near_v1, pos, near_v2));//bottom
		frustum.setPlane(5, BPlane(-direct, pos + direct * far));//far

		dirty = false;
	}
	return true;
}

bool member_BLight::prepare(const BScene* scene)
{
	if (!freshFrustum())
		return false;

	BVector position = boss->position();
	modelViewMatrix.lookAt(position, center, up);
	projectionMatrix.frustum(-halfSize, halfSize, -halfSize, halfSize, near, far);
	entireMatrix = projectionMatrix * modelViewMatrix;

	shapeArray.reset();
	transShapeQueue.reset();
	if (scene_member(scene)->sceneOctTree)
	{
		sceneItems.reset();
		scene_member(scene)->sceneOctTree->intersect(frustum.space(), sceneItems);
		for (int i = 0; i < sceneItems.size(); i++)
		{
			SceneOctTreeItem& item = sceneItems[i];
			prepare(item);
		}
	}
	else
	{
		sceneItems.clear();
		BSet<BNodeHolder>& nodes = scene_member(scene)->nodes;
		BSet<BShapeHolder>& shapes = scene_member(scene)->shapes;
		prepareShapes(nodes, shapes);
		BSet<BGroupHolder>& groups = scene_member(scene)->groups;
		for (int i = 0; i < groups.size(); i++)
		{
			prepare(groups[i]);
		}
	}

	return true;
}
void member_BLight::prepare(SceneOctTreeItem& item)
{
	BArray<BNodeHolder>& nodes = item->nodes;
	for (int i = 0; i < nodes.size(); i++)
	{
		BNode* node = nodes[i];
		if (node->visible() == false)
			continue;

		if (frustum.clip(node->space()))
			continue;

		const BMatrix& nodeMatrix = node->matrix();
		for (int i = 0; i < node->shapeCount(); i++)
		{
			BShape* shape = node->shape(i);
			if (!shape->visible())
				continue;

			BMatrix matrix = shape->matrix();
			if (!shape_member(shape)->isolate)
				matrix *= nodeMatrix;
			BBox box = shape->box() * matrix;
			if (frustum.clip(box.space()))
				continue;

			ShapeInfo sinfo;
			sinfo.shape = shape;
			sinfo.node = shape_member(shape)->isolate ? 0 : node;
			sinfo.space = box.space();
			if (shape_member(shape)->transparent)
			{
				BReal dist = frustum.plane(0).distance(box.center());
				transShapeQueue.insert(dist, sinfo);
			}
			else
			{
				shapeArray.append(sinfo);
			}
		}
	}
	BArray<BShapeHolder>& shapes = item->shapes;
	for (int i = 0; i < shapes.size(); i++)
	{
		BShape* shape = shapes[i];
		if (!shape->visible())
			continue;

		const BSpace& space = shape->space();
		if (frustum.clip(space))
			continue;

		ShapeInfo sinfo;
		sinfo.shape = shape;
		sinfo.space = space;
		if (shape_member(shape)->transparent)
		{
			BReal dist = frustum.plane(0).distance(space.center());
			transShapeQueue.insert(dist, sinfo);
		}
		else
		{
			shapeArray.append(sinfo);
		}
	}
}
void member_BLight::prepare(BGroup* group)
{
	const BString& name = group->name();
	if (group_member(group)->visible)
	{
		BSet<BNodeHolder>& nodes = group_member(group)->nodes;
		BSet<BShapeHolder>& shapes = group_member(group)->shapes;
		prepareShapes(nodes, shapes);
		BSet<BGroupHolder>& children = group_member(group)->children;
		for (int i = 0; i < children.size(); i++)
		{
			prepare(children[i]);
		}
	}
}
void member_BLight::prepareShapes(BSet<BNodeHolder>& nodes, BSet<BShapeHolder>& shapes)
{
	for (int i = 0; i < nodes.size(); i++)
	{
		BNode* node = nodes[i];
		if (node->visible() == false)
			continue;

		if (frustum.clip(node->space()))
			continue;

		const BMatrix& nodeMatrix = node->matrix();
		for (int i = 0; i < node->shapeCount(); i++)
		{
			BShape* shape = node->shape(i);
			if (!shape->visible())
				continue;

			BMatrix matrix = shape->matrix();
			if (!shape_member(shape)->isolate)
				matrix *= nodeMatrix;

			BBox box = shape->box() * matrix;
			if (frustum.clip(box.space()))
				continue;

			ShapeInfo sinfo;
			sinfo.shape = shape;
			sinfo.node = shape_member(shape)->isolate ? 0 : node;
			sinfo.space = box.space();
			if (shape_member(shape)->transparent)
			{
				BReal dist = frustum.plane(0).distance(box.center());
				transShapeQueue.insert(dist, sinfo);
			}
			else
			{
				shapeArray.append(sinfo);
			}
		}
	}
	for (int i = 0; i < shapes.size(); i++)
	{
		BShape* shape = shapes[i];
		if (!shape->visible())
			continue;

		const BSpace& space = shape->space();
		if (frustum.clip(space))
			continue;

		ShapeInfo sinfo;
		sinfo.shape = shape;
		sinfo.space = space;
		if (shape_member(shape)->transparent)
		{
			BReal dist = frustum.plane(0).distance(space.center());
			transShapeQueue.insert(dist, sinfo);
		}
		else
		{
			shapeArray.append(sinfo);
		}
	}
}

void member_BLight::frameShadow(const BScene* scene)
{
	GLint attachmentNumber;
	glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &attachmentNumber);

	int width = shadowSize;
	int height = shadowSize;
	if (fbo == 0 || rbo == 0 || tbo == 0)
		resized = true;
	if (resized)
	{
		GLint backup_frame_buffer;
		glGetIntegerv(GL_FRAMEBUFFER_BINDING, &backup_frame_buffer);
		if (samples > 0)
		{
			if (!msfbo)
			{
				glGenFramebuffers(1, &msfbo);
				glGenRenderbuffers(1, &msrbo);
				glGenTextures(1, &mstbo);
			}
			if (msfbo)
			{
				glBindFramebuffer(GL_FRAMEBUFFER, msfbo);

				glBindRenderbuffer(GL_RENDERBUFFER, msrbo);
				glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, width, height);
				glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msrbo);

				glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mstbo);
				glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGBA, width, height, GL_TRUE);
				glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D_MULTISAMPLE, mstbo, 0);

				GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
				if (status != GL_FRAMEBUFFER_COMPLETE)
					return;
			}
		}
		else
		{
			if (msfbo)
			{
				UnusedFrameBufferObjects.append(msfbo);
				msfbo = 0;
			}
			if (msrbo)
			{
				UnusedRenderBufferObjects.append(msrbo);
				msrbo = 0;
			}
			if (mstbo)
			{
				UnusedTextureObjects.append(mstbo);
				mstbo = 0;
			}
		}
		if (!fbo)
		{
			glGenFramebuffers(1, &fbo);
			glGenRenderbuffers(1, &rbo);
		}
		if (fbo)
		{
			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
			glBindRenderbuffer(GL_RENDERBUFFER, rbo);
			glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

		}
		if (!tbo)
		{
			glGenTextures(1, &tbo);
		}
		if (tbo)
		{
			glBindTexture(GL_TEXTURE_2D, tbo);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tbo, 0);
		}
		glBindFramebuffer(GL_FRAMEBUFFER, backup_frame_buffer);
		resized = false;
	}

	if (!prepare(scene))
		return;

	BRender render(boss);

	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	glClearDepth(0.31);
	glClear(GL_DEPTH_BUFFER_BIT);
	glDepthMask(GL_TRUE);

	for (int i = 0; i < shapeArray.size(); i++)
	{
		ShapeInfo& sinfo = shapeArray[i];
		drawShape(sinfo, &render);
	}
	for (auto it = transShapeQueue.rbegin(); it != transShapeQueue.rend(); --it)
	{
		ShapeInfo& sinfo = *it;
		drawShape(sinfo, &render);
	}
}
void member_BLight::drawShape(ShapeInfo& sinfo, BRender* render)
{
	BNode* node = sinfo.node;
	BShape* shape = sinfo.shape;

	glPushMatrix();

	render->setDepthTest(shape_member(shape)->depthTest);
	render->setDepthOffset(shape_member(shape)->depthOffset);
	render->setCullback(shape_member(shape)->cullback);
	render->setPointSmooth(shape_member(shape)->pointSmooth);
	render->setPointSize(shape_member(shape)->pointSize);
	render->setLineSmooth(shape_member(shape)->lineSmooth);
	render->setLineWidth(shape_member(shape)->lineWidth);
	render->setPolySmooth(shape_member(shape)->polySmooth);
	render->setPolyMode(shape_member(shape)->polyMode);

	if (node)
		BOpenGL::glMultMatrix(node_member(node)->matrix);

	BOpenGL::glMultMatrix(shape_member(shape)->matrix);

	shape_member(shape)->render(*render);

	glPopMatrix();
}
